/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 *
 */
package com.google.code.bigyang.buffer;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;



public abstract class AbstractNioBuffer implements NioBuffer {

	private boolean autoExpand;
	
	private boolean autoShrink;
	
	public abstract boolean isDirect();
	
	public Buffer clear() {
		buf().clear();
		return this;
	}
	
	public boolean hasRemaining() {
		return buf().hasRemaining();
	}
	
	public int limit() {
		return buf().limit();
	}
	
	public int position() {
		return buf().position();
	}
	
	public int capacity() {
		return buf().capacity();
	}
	
	public Buffer order(Order order) {
		if(order.name().equals(Order.BIG_ENDIAN)) {
			buf().order(ByteOrder.BIG_ENDIAN);
		} else if(order.name().equals(Order.LITTLE_ENDIAN)) {
			buf().order(ByteOrder.LITTLE_ENDIAN);
		}
		return this;
	}
	
	public Order getOrder() {
		ByteOrder order = buf().order();
		if(order.toString().equals(ByteOrder.BIG_ENDIAN)) {
			return Order.BIG_ENDIAN;
		} else if(order.toString().equals(ByteOrder.LITTLE_ENDIAN)) {
			return Order.LITTLE_ENDIAN;
		}
		return Order.BIG_ENDIAN;
	}
	
	public void filp() {
		buf().flip();
	}
	
	public abstract ByteBuffer buf();

	
	
	
	
	
	@SuppressWarnings("static-access")
	public Buffer wrap(byte[] buf) {
		buf().wrap(buf);
		return this;
	}
	
	@SuppressWarnings("static-access")
	public Buffer wrap(byte[] buf,int offset,int len) {
		buf().wrap(buf, offset, len);
		return this;
	}

	public Buffer compact() {
		buf().compact();
		return this;
	}

	public ByteBuffer duplicate() {
		return buf().duplicate();
		
	}

	public ByteBuffer slice() {
		return buf().slice();
	}

	public ByteBuffer asReadOnlyBuffer() {
		return buf().asReadOnlyBuffer();
	}

	public boolean hasArray() {
		return buf().hasArray();
	}

	public byte[] array() {
		return buf().array();
	}

	public Buffer free() {
		return this;
	}

	public boolean isReadOnly() {
		return buf().isReadOnly();
	}

	
	
	public boolean isAutoExpand() {
		return autoExpand;
	}

	public Buffer setAutoExpand(boolean autoExpand) {
		this.autoExpand = autoExpand;
		return this;
	}

	public abstract Buffer expand(int size);

//	public Buffer expand(int position, int size) {
//		// TODO Auto-generated method stub
//		return null;
//	}

	public boolean isAutoShrink() {
		return autoShrink;
	}

	public Buffer setAutoShrink(boolean autoShrink) {
		this.autoShrink = autoShrink;
		return this;
	}

	public Buffer shrink() {
		return this;
	}

	public Buffer mark() {
		buf().mark();
		return this;
	}

	public Buffer reset() {
		buf().reset();
		return this;
	}

	public InputStream asInputStream() {
		return new NioByteBufferInpuStream();
	}

	
	
	private class NioByteBufferInpuStream extends InputStream {
		@Override
		public int read() throws IOException {
			if(AbstractNioBuffer.this.hasRemaining()) {
				return AbstractNioBuffer.this.getByte() & 0xFF;
			} else {
				return -1;
			}
		}

		@Override
		public int read(byte[] b) throws IOException {
			return read(b, 0, b.length);
		}

		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			int remaining = AbstractNioBuffer.this.remaining();
			if(remaining > 0) {
				int readBytes = Math.min(remaining, len);
				AbstractNioBuffer.this.getBytes(b, off, readBytes);
				return readBytes;
			}
			return - 1;
		}

		@Override
		public long skip(long n) throws IOException {
			int skipBytes;
			if(n > Integer.MAX_VALUE) {
				skipBytes = AbstractNioBuffer.this.remaining();
			} else {
				skipBytes = Math.min(AbstractNioBuffer.this.remaining(), (int)n);
			}
			return skipBytes;
		}

		@Override
		public int available() throws IOException {
			return AbstractNioBuffer.this.limit() - AbstractNioBuffer.this.position();
		}

		@Override
		public void close() throws IOException {
			
		}

		@Override
		public synchronized void mark(int readlimit) {
			AbstractNioBuffer.this.mark();
		}

		@Override
		public synchronized void reset() throws IOException {
			AbstractNioBuffer.this.reset();
		}

		@Override
		public boolean markSupported() {
			return true;
		}
	}
	
	
	
	
	public OutputStream asOutputStream() {
		return new NioByteBufferOutputStream();
	}
	
	private class NioByteBufferOutputStream extends OutputStream {
		@Override
		public void write(int b) throws IOException {
			AbstractNioBuffer.this.putByte((byte)b);
			
		}

		@Override
		public void write(byte[] b) throws IOException {
			write(b, 0, b.length);
		}

		@Override
		public void write(byte[] b, int off, int len) throws IOException {
			super.write(b, off, len);
			AbstractNioBuffer.this.putBytes(b, off, len);
		}

		@Override
		public void flush() throws IOException {

		}

		@Override
		public void close() throws IOException {

		}
		
	}
	

	public Buffer putByte(byte b) {
		buf().put(b);
		return this;
	}

	public Buffer putByte(int index, byte b) {
		buf().put(index, b);
		return this;
	}
	
	public Buffer putBytes(byte[] buf) {
		buf().put(buf);
		return this;
	}
	
	public Buffer putBytes(byte[] buf,int offset,int len) {
		buf().put(buf, offset, len);
		return this;
	}

	public byte getByte() {
		return buf().get();
	}

	public byte getByte(int index) {
		return buf().get(index);
	}

	public Buffer getBytes(byte[] buf) {
		buf().get(buf);
		return this;
	}
	
	public Buffer getBytes(byte[] buf,int offset,int len) {
		buf().get(buf, offset, len);
		return this;
	}
	
	
	public Buffer putShort(short s) {
		buf().putShort(s);
		return this;
	}

	public Buffer putShort(int index, short s) {
		buf().putShort(index, s);
		return this;
	}

	public short getShort() {
		return buf().getShort();
	}

	public short getShort(int index) {
		return buf().getShort(index);
	}

	public Buffer putInt(int i) {
		buf().putInt(i);
		return this;
	}

	public Buffer putInt(int index, int i) {
		buf().putInt(index, i);
		return this;
	}

	public int getInt() {
		return buf().getInt();
	}

	public int getInt(int index) {
		return buf().getInt(index);
	}

	public Buffer putLong(long v) {
		buf().putLong(v);
		return this;
	}

	public Buffer putLong(int index, long v) {
		buf().putLong(index, v);
		return this;
	}

	public long getLong() {
		return buf().getLong();
	}

	public long getLong(int index) {
		return buf().getLong(index);
	}

	public Buffer putFloat(float f) {
		buf().putFloat(f);
		return this;
	}

	public Buffer putFloat(int index, float f) {
		buf().putFloat(index, f);
		return this;
	}

	public float getFloat() {
		return buf().getFloat();
	}

	public float geteFloat(int index) {
		return buf().getFloat(index);
	}

	public Buffer putDouble(double d) {
		buf().putDouble(d);
		return this;
	}

	public Buffer putDouble(int index, double d) {
		buf().putDouble(index, d);
		return this;
	}

	public double getDouble() {
		return buf().getDouble();
	}

	public double getDouble(int index) {
		return buf().getDouble(index);
	}

	public ShortBuffer asShortByte() {
		return buf().asShortBuffer();
	}

	public CharBuffer asCharByte() {
		return buf().asCharBuffer();
	}

	public IntBuffer asIntBuffer() {
		return buf().asIntBuffer();
	}

	public LongBuffer asLongBuffer() {
		return buf().asLongBuffer();
	}

	public FloatBuffer asFloatBuffer() {
		return buf().asFloatBuffer();
	}

	public DoubleBuffer asDoubleBuffer() {
		return buf().asDoubleBuffer();
	}

	public int remaining() {
		return buf().remaining();
	}
	
	
	
}
